﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Ports;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;

namespace RCU_Final
{
    public class RangeFinder
    {
        #region Variables

        private int baudRate;
        private Parity parity;
        private int dataBits;
        private StopBits stopBits;
        private SerialPort serialPort;
        private Thread receiveThread;
        private volatile Boolean shouldStop;
        private object dataLock;
        private int[] convertedData;

        /*serial port parameters*/
        /// <summary>
        /// Serial port name
        /// </summary>
        public string portName { get; set; }
        /// <summary>
        /// Starting step - minimum of 44
        /// </summary>
        public int startStep { get; set; }
        /// <summary>
        /// Ending step - maximum of 725
        /// </summary>
        public int endStep { get; set; }
        /// <summary>
        /// Cluster count
        /// </summary>
        public int clusterCount { get; set; }

        #endregion

        #region Constructors
        /// <summary>
        /// Default Constructor: serial Port=COM1, start=44, end=725, cluster=1
        /// </summary>
        public RangeFinder()
            : this("COM3", 44, 725, 1)
        {
        }
        /// <summary>
        /// Create RangeFinder given the port name.
        /// </summary>
        /// <param name="port"></param>
        public RangeFinder(string port)
            : this(port, 44, 725, 1)
        {
        }
        /// <summary>
        /// Create RangeFinder given the port name and start/end step and cluster count
        /// </summary>
        /// <param name="port"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="cc"></param>
        public RangeFinder(string port, int start, int end, int cc)
        {
            portName = port;
            baudRate = 19200;
            parity = Parity.None;
            dataBits = 8;
            stopBits = StopBits.One;
            dataLock = new object();
            startStep = start;
            endStep = end;
            clusterCount = cc;
            shouldStop = true;
        }

        #endregion

        #region Methods
        /// <summary>
        /// Opens serial port connection and starts asynchronous monitor
        /// </summary>
        public void Start()
        {
            if (receiveThread != null && receiveThread.IsAlive)
                throw new Exception("Instance of monitor thread is still active.");
            try
            {
                /*check parameters for amin/amax/cluster size*/
                if (startStep > endStep || startStep < 44 || endStep > 725 || clusterCount < 1 || clusterCount > 99)
                    throw new Exception("Invalid start/end step or cluster count value.");
                /*create instance of serial port*/
                serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
                /*set timeout*/
                serialPort.ReadTimeout = 1000;
                serialPort.WriteTimeout = 1000;
                /*open connection*/
                serialPort.Open();
                /*reset sensor, turn on motor*/
                serialPort.Write("RS\n");
                serialPort.Write("BM\n");
                serialPort.Write("MS" + startStep.ToString().PadLeft(4, '0') + endStep.ToString().PadLeft(4, '0') + clusterCount.ToString().PadLeft(2, '0') + "000\n");
                /*launch monitor*/
                shouldStop = false;
                receiveThread = new Thread(new ThreadStart(Monitor));
                receiveThread.Start();
            }
            catch (Exception ex)
            {
                if (serialPort != null)
                    serialPort.Close();
                throw;
            }
        }

        /// <summary>
        /// Asynchronous monitoring thread function
        /// </summary>
        private void Monitor()
        {
            string buffer;
            int[] tempData;
            /*read data from serial port...*/
            while (!shouldStop && serialPort.IsOpen)
            {
                try
                {
                    buffer = serialPort.ReadTo("\n\n");
                    /*parse & convert data*/
                    string[] lines = buffer.Split('\n');
                    /*for there to be data, must be at least 4 lines*/
                    if (lines.Length < 4)
                        continue;
                    /*check for signature value in line 2*/
                    if (!lines[1].Equals("99b")) continue;
                    /*compute size of needed array*/
                    int dataSize = (lines[lines.Length - 1].Length - 1) / 2 + (lines[3].Length - 1) / 2 * (lines.Length - 4);
                    tempData = new int[dataSize];
                    for (int i = 3; i < lines.Length; i++)
                    {
                        int sum = 0;
                        for (int j = 0; j < (lines[i].Length) - 1; j += 2)
                        {
                            tempData[(i - 3) * 32 + (j / 2)] = getVal(lines[i].Substring(j, 2));
                            sum += (int)lines[i][j];
                            sum += (int)lines[i][j + 1];
                        }
                        /*compute error checksum*/
                        int checksum = (sum & 0x3f) + 0x30;
                        if ((int)(lines[i][lines[i].Length - 1]) != checksum)
                        {
                            throw new Exception("Corrupted data received");
                        }
                    }
                    /*store new data*/
                    lock (dataLock)
                    {
                        convertedData = tempData;
                    }
                }
                catch (Exception ex)
                {
                    Debug.Write(ex.ToString());
                    /*ignoring exceptions...*/
                    continue;
                }
            }
            serialPort.Write("QT\n");
            serialPort.Close();

        }

        /// <summary>
        /// Retrieves most recent sensor output
        /// </summary>
        /// <returns></returns>
        public int[] getData()
        {
            int[] data = null;
            lock (dataLock)
            {
                if (convertedData != null)
                    data = (int[])convertedData.Clone();
            }
            return data;
        }

        /// <summary>
        /// Stops asynchronous monitor and closes serial port connection
        /// </summary>
        public void Stop()
        {
            shouldStop = true;
        }

        private int getVal(string val)
        {
            int value = 0;
            for (int i = 0; i < val.Length; i++)
                value += (val[val.Length - 1 - i] - 0x30) << (i * 6);
            return value;
        }

        #endregion
    }

}
